﻿using System;
using System.Data.Common;
using System.Linq;
using System.Threading.Tasks;

using Utilities;

namespace DataAccess
{
    public abstract class QueryDatabaseCommand<T> : DatabaseCommand, IDatabaseReader
    {
        /// <summary>
        /// The map of types to type code returned by the query
        /// </summary>
        public TypeMap TypeMap { get; set; }

        /// <summary>
        /// The map of the properties to the index to call the reader
        /// </summary>
        public PropertyMap PropertyMap { get; set; }

        /// <summary>
        /// The delegate called when a record is found
        /// </summary>
        public Action<DbDataReader, T> OnRecord { get; set; }

        protected override int Execute(DbCommand command)
        {
            using (DbDataReader reader = command.ExecuteReader())
            {
                return Read(reader);
            }
        }

        protected override async Task<int> ExecuteAsync(DbCommand command)
        {
            using (DbDataReader reader = await command.ExecuteReaderAsync())
            {
                return Read(reader);
            }
        }

        public abstract int Read(DbDataReader reader);

        protected object ReadObject(DbDataReader reader)
        {
            object obj;

            if (TypeMap != null)
            {
                obj = TypeMap.CreateObject(reader);
            }
            else
            {
                obj = typeof(T).CreateInstance();
            }

            if (OnRecord != null)
            {
                OnRecord(reader, (T)obj);
            }
            else // Read the record using reflexion and/or a property map
            {
                ReadRecord(reader, (T)obj);
            }

            return obj;
        }

        private void ReadRecord(DbDataReader reader, T obj)
        {
            TypeAccessor ta = obj.GetTypeAccessor();

            foreach (PropertyAccessor pa in ta.PropertyAccessors.Values.Where(a => a.IsPrimitive))
            {
                if (pa.CanSet)
                {
                    int i = -1;

                    if (PropertyMap != null)
                    {
                        if (PropertyMap.IsIgnored(pa.PropertyName))
                        {
                            continue; // Do nothing
                        }

                        i = PropertyMap.GetIndex(pa.PropertyName);
                    }

                    if (i == -1)
                    {
                        i =  reader.GetOrdinal(pa.PropertyName);
                    }

                    object value = reader.IsDBNull(i) ? null : reader[i];
                    pa.SetValue(obj, value);
                }
            }
        }
    }
}
